/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.interceptor;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jrose.api.annotation.cache.CacheEvict;
import org.jrose.api.annotation.cache.Cacheable;
import org.jrose.core.cache.UseCachePolicy;
import org.jrose.core.jdbc.datasource.UseRoutingPolicy;
import org.jrose.core.util.ApplicationContextProvider;
import org.jrose.core.util.ClassUtils;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;

import javax.inject.Named;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Named
@Slf4j
@NoArgsConstructor
public class AnnotationBeanPostProcessor extends ApplicationContextProvider implements BeanPostProcessor, BeanClassLoaderAware {
    private static final Set<Class<? extends Annotation>> annotations;

    static {
        annotations = new HashSet<>();
        annotations.add(Cacheable.class);
        annotations.add(CacheEvict.class);
        annotations.add(UseRoutingPolicy.class);
        annotations.add(UseCachePolicy.class);
    }

    @Setter private ClassLoader beanClassLoader;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean == null) {
            return bean;
        }
        boolean annotationFound = false;
        List<Class<?>> classes = ClassUtils.getClassInheritanceHierachy(bean.getClass());
        for (Class<?> clazz : classes) {
            if (clazz == Object.class) {
                continue;
            }
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
                    // ignore all static and non-public methods
                    continue;
                }
                for (Class<? extends Annotation> annotationClass : annotations) {
                    if (method.isAnnotationPresent(annotationClass)) {
                        annotationFound = true;
                        break;
                    }
                }
                if (annotationFound) break;
            }
        }
        if (!annotationFound) {
            return bean;
        }
        AnnotationMethodInterceptor interceptor = new AnnotationMethodInterceptor(getApplicationContext(), bean);
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.setTarget(bean);
        proxyFactory.addAdvice(interceptor);
        return proxyFactory.getProxy(beanClassLoader);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
